/*
 * main.cpp
 *
 *  Created on: 11 déc. 2013
 *      Author: Raphaël
 */

#include <memory>
#include <vector>
#include <algorithm>
#include <iostream>
#include <string>
#include <map>
#include <functional>
#include <numeric>
#include <iterator>
#include "raphTools/Random.h"
#include "test/Sorter.h"
#include "raphTools/Tools.h"
#include "raphTools/Chrono.h"
#include "raphTools/WorkspaceTools.h"
#include "test/D.h"
#include "test/A.h"
#include "test/MyString.h"
#include "tree/Tree.hpp"
#include "tree/BinaryTree.hpp"
#include "raphTools/exceptions/exceptions.hpp"
#include "raphTools/functors/functors.hpp"
#include "raphTools/StringTokenizer.hpp"

using namespace raph::tools::exceptions;
using namespace raph::tools::functors;
using namespace raph::type;

template<typename Data, class Accessor = raph::tools::functors::Identity>
static double average(Data begin, const Data& end, const Accessor &accessor = Accessor())
    {
    if (begin == end)
        {
        throw EmptyCollectionException(RaphException::info(__FILE__, __LINE__));
        }

    typedef typename std::iterator_traits<Data>::value_type T;
    return std::accumulate(begin, end, 0.0, [&accessor](double x, T &y)
        {return x + accessor(y);}) / std::distance(begin, end);
    }

template<typename Data, class Accessor = raph::tools::functors::Identity>
static double median(Data begin, const Data& end, const Accessor &accessor = Accessor())
    {
    if (begin == end)
        {
        throw EmptyCollectionException(RaphException::info(__FILE__, __LINE__));
        }

    std::size_t size = std::distance(begin, end);
    std::size_t middle = size / 2;
    Data target = begin + middle;
    std::nth_element(begin, target, end);

    if (size % 2 != 0)
        {
        return accessor(*target);
        }
    else
        {
        double a = accessor(*target);
        Data targetNeighbor = target - 1;
        std::nth_element(begin, targetNeighbor, end);
        return (a + accessor(*targetNeighbor)) / 2.0;
        }
    }

template<typename DataIn, typename DataOut, class AccessorIn = raph::tools::functors::Identity,
        class AccessorOut = raph::tools::functors::Identity>
static void myTest(DataIn inBegin, const DataIn& inEnd, DataOut outBegin, const DataOut& outEnd, const AccessorIn &accessorIn =
        AccessorIn(), const AccessorOut &accessorOut = AccessorOut())
    {
    if (std::distance(inBegin, inEnd) != std::distance(outBegin, outEnd))
        {
        throw NotSameLengthCollectionsException();
        }
    DataIn inIt = inBegin;
    DataOut outIt = outBegin;
    for (; inIt != inEnd; ++inIt, ++outIt)
        {
        accessorOut(*outIt) = accessorIn(*inIt) * 5;
        }
    }

template<typename DataIn, typename DataOut, class Function, class AccessorIn = raph::tools::functors::Identity,
        class AccessorOut = raph::tools::functors::Identity>
static void myTest2(
        DataIn inBegin,
        const DataIn& inEnd,
        DataOut outBegin,
        const DataOut& outEnd,
        Function fct,
        const AccessorIn &accessorIn = AccessorIn(),
        const AccessorOut &accessorOut = AccessorOut())
    {
    if (std::distance(inBegin, inEnd) != std::distance(outBegin, outEnd))
        {
        throw NotSameLengthCollectionsException();
        }
    DataIn inIt = inBegin;
    DataOut outIt = outBegin;
    for (; inIt != inEnd; ++inIt, ++outIt)
        {
        accessorOut(*outIt) = fct(accessorIn(*inIt));
        }
    }

int main(int, char**)
    {
    std::string str("1,2,3,4");
    ch::raph::tools::StringTokenizer s;
    s(str.begin(), str.end(), ",");
    std::list<int> list();
    list.push_back(10);
    list.push_back(20);
    list.push_back(30);
    list.push_back(40);

    double _average = average(list.begin(), list.end());
    std::cout << _average << std::endl;
    /////////////////////////////////////////////

    int tab[4] =
        {
        };
    tab[0] = 30;
    tab[1] = 40;
    tab[2] = 50;
    tab[3] = 60;

    _average = average(tab, tab + 4);
    std::cout << _average << std::endl;
    /////////////////////////////////////////////

    typedef std::pair<int, int> IntInt;
    typedef std::list<IntInt> IntIntList;
    IntIntList listPair;
    listPair.push_back(IntInt(100, 200));
    listPair.push_back(IntInt(200, 300));
    listPair.push_back(IntInt(300, 400));
    listPair.push_back(IntInt(400, 500));

    _average = average(listPair.begin(), listPair.end(), PairSecondValue());
    std::cout << _average << std::endl;
    /////////////////////////////////////////////

    try
        {
        typedef std::pair<std::string, double> StrDbl;
        std::vector<StrDbl> vec();
        vec.push_back(StrDbl("a", 1000.0));
        vec.push_back(StrDbl("b", 2000.0));
        vec.push_back(StrDbl("c", 3000.0));
        vec.push_back(StrDbl("c", 40000.0));

        double _median = median(vec.begin(), vec.end(), PairSecondValue());
        _average = average(vec.begin(), vec.end(), PairSecondValue());
        std::cout << _average << std::endl;
        std::cout << _median << std::endl;

        }
    catch (std::exception &e)
        {
        std::cerr << e.what() << std::endl;
        }
    /////////////////////////////////////////////

    typedef std::pair<T_UINT16, T_FLOAT128> MyMapPair;
    typedef std::map<T_UINT16, T_FLOAT128> MyMap;
    MyMap map;
    map.insert(MyMapPair(4, 100));
    map.insert(MyMapPair(5, 200));
    map.insert(MyMapPair(6, 300));

    _average = average(map.begin(), map.end(), PairFirstValue());
    std::cout << _average << std::endl;
    /////////////////////////////////////////////

        {
        typedef std::vector<double> dblVec;

        int tab[] =
            {
            1, 2, 3, 4
            };
        dblVec vec(4, 0);

        myTest(tab, tab + 4, vec.begin(), vec.end());
        for (auto &n : vec)
            {
            std::cout << n << std::endl;
            }
        }
    /////////////////////////////////////////////

    }
